Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/deseng692 and deseng671: Updated video widget, made crucial changes to authoring loaders and actions, added columns to engagement and widget_video tables. #2592

Merged
merged 6 commits into from
Sep 20, 2024

Conversation

jareth-whitney
Copy link
Contributor

Issue #: https://citz-gdx.atlassian.net/browse/DESENG-692
Issue #: https://citz-gdx.atlassian.net/browse/DESENG-671

Description of changes:

  • Implemented video widget Figma design
  • Created custom layover bar for videos that shows video provider and has a custom logo
  • Transcripts will not be implemented at this time
  • Updated db to add the column "title" to "widget_video" table. As per design, user should be able to set custom title.
  • Updated db to add the column "description_title" to "engagement" table to accomodate the section header for the description
  • Also updated translation data structures as needed
  • Made fixes to the authoring section for better loading and actions

User Guide update ticket (if applicable):
N/A

@codecov-commenter
Copy link

codecov-commenter commented Sep 19, 2024

Codecov Report

Attention: Patch coverage is 32.43243% with 50 lines in your changes missing coverage. Please review.

Project coverage is 75.12%. Comparing base (5373540) to head (132ccb0).

Files with missing lines Patch % Lines
...agement/old-view/widgets/Video/VideoWidgetView.tsx 15.78% 48 Missing ⚠️
...src/met_api/services/widget_translation_service.py 0.00% 1 Missing ⚠️
...s/engagement/form/EngagementWidgets/Video/Form.tsx 75.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2592      +/-   ##
==========================================
- Coverage   75.27%   75.12%   -0.15%     
==========================================
  Files         612      612              
  Lines       22145    22215      +70     
  Branches     1839     1859      +20     
==========================================
+ Hits        16669    16689      +20     
- Misses       5203     5253      +50     
  Partials      273      273              
Flag Coverage Δ
metapi 87.15% <90.90%> (+<0.01%) ⬆️
metweb 64.12% <22.22%> (-0.24%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
met-api/src/met_api/models/engagement.py 78.97% <100.00%> (+0.10%) ⬆️
...t-api/src/met_api/models/engagement_translation.py 96.96% <100.00%> (+0.04%) ⬆️
met-api/src/met_api/models/widget_translation.py 98.03% <100.00%> (+0.03%) ⬆️
met-api/src/met_api/models/widget_video.py 100.00% <100.00%> (ø)
met-api/src/met_api/schemas/engagement.py 93.44% <100.00%> (+0.10%) ⬆️
...-api/src/met_api/schemas/engagement_translation.py 100.00% <100.00%> (ø)
met-api/src/met_api/schemas/widget_translation.py 100.00% <100.00%> (ø)
met-api/src/met_api/schemas/widget_video.py 100.00% <100.00%> (ø)
met-api/src/met_api/services/engagement_service.py 71.07% <ø> (ø)
...met_api/services/engagement_translation_service.py 89.00% <100.00%> (+0.11%) ⬆️
... and 7 more

<ControlledTextField
name="title"
variant="outlined"
label=" "
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there some other labelling happening here for screen readers?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Alex, I'll create some labels for screen readers :)

banner_filename: string;
status_block: string[];
title: string;
icon_name: string;
metadata_value: string;
send_report: boolean;
send_report: boolean | undefined;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
send_report: boolean | undefined;
send_report?: boolean;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need a state for the boolean that is neither true or false, so that if that field isn't set, it isn't submitted in the action. I'm open to suggestions if there's a better way to do it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The question mark means that it's optional; i.e. it can be undefined

banner_filename: data.banner_filename,
status_block: data.status_block,
title: data.title,
icon_name: data.icon_name,
metadata_value: data.metadata_value,
send_report: getSendReportValue(data.send_report),
send_report: data.send_report ? getSendReportValue(data.send_report) : '',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the getSendReportValue function - this code replicates the same behaviour. If you want it to store the string "false" instead of "" when send_report is turned off, replace the || with a ?? nullish coalescing operator.

Suggested change
send_report: data.send_report ? getSendReportValue(data.send_report) : '',
send_report: (data.send_report || '').toString()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see the complete solution now. I'll change the logic.

// Update the loader data when the authoring section is changed, by triggering navigation().
const navigate = useNavigate();
useEffect(() => {
engagementData.then(() => navigate('.', { replace: true }));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strongly consider using revalidator.revalidate() for this

@@ -42,25 +47,48 @@ const AuthoringSummary = () => {
}
}, [fetcher.data]);

const { content } = useRouteLoaderData('authoring-loader') as {
content: Promise<EngagementContent[]>;
const untouchedDefaultValues: EngagementUpdateData = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there such a thing as "touched default values"? What does "default" mean here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The defaultValues are the defaultValues for the form. They are passed down from the AuthoringContext and so is setDefaultValues, so that we have full control over the value. It represents the "untouched" state of the form, which will change based on the values that we load from the db. So the regular defaultValues are whatever is loaded into the form. The untouched default values are the blank default values that are never changed.

## September 12, 2024

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make sure to also document the new work you did for the Summary section :)

Comment on lines +66 to +84
const formatVideoDuration = (duration: number) => {
let minutes = 0;
let hours = 0;
let seconds = 0;
if (3600 < duration) {
hours = Math.floor(duration / 3600);
minutes = Math.floor((duration - hours * 3600) / 60);
seconds = Math.floor(duration - hours * 3600 - minutes * 60);
return `${hours} hr ${minutes} min ${seconds} sec`;
} else if (3600 > duration && 60 < duration) {
minutes = Math.floor(duration / 60);
seconds = Math.floor(duration - minutes * 60);
return `${minutes} min ${seconds} sec`;
} else if (60 > duration && 0 < duration) {
return `${seconds} seconds`;
} else {
return '';
}
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const formatVideoDuration = (duration: number) => {
let minutes = 0;
let hours = 0;
let seconds = 0;
if (3600 < duration) {
hours = Math.floor(duration / 3600);
minutes = Math.floor((duration - hours * 3600) / 60);
seconds = Math.floor(duration - hours * 3600 - minutes * 60);
return `${hours} hr ${minutes} min ${seconds} sec`;
} else if (3600 > duration && 60 < duration) {
minutes = Math.floor(duration / 60);
seconds = Math.floor(duration - minutes * 60);
return `${minutes} min ${seconds} sec`;
} else if (60 > duration && 0 < duration) {
return `${seconds} seconds`;
} else {
return '';
}
};
const formatVideoDuration = (duration: number) => {
if (duration <= 0) return '';
const hours = Math.floor(duration / 3600);
const minutes = Math.floor((duration % 3600) / 60);
const seconds = Math.floor(duration % 60);
let result = '';
if (hours > 0) result += `${hours} hr `;
if (minutes > 0) result += `${minutes} min `;
if (seconds > 0 || result === '') result += `${seconds} sec`;
return result.trim();
};

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a couple of reasons why I didn't implement it this way:

  • IMO you would still want all subsequent fields from the biggest unit that has a value, even if the subsequent value is 0
  • Although my own code is maybe 4 lines longer and is more basic, I find it more readable

Comment on lines 86 to 105
const getVideoSource = (url: string) => {
const hostname = new URL(url).hostname;
if ('youtube.com' === hostname || 'youtu.be' === hostname) {
return 'YouTube';
} else if ('vimeo.com' === hostname) {
return 'Vimeo';
} else if ('facebook.com' === hostname) {
return 'Facebook';
} else if ('twitch.tv' === hostname) {
return 'Twitch';
} else if ('soundcloud.com' === hostname) {
return 'SoundCloud';
} else if ('mixcloud.com' === hostname) {
return 'Mixcloud';
} else if ('dailymotion.com' === hostname) {
return 'DailyMotion';
} else {
return 'Unknown';
}
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace this large if/else/else... statement with something like an object map.

const domains = {
    'youtube.com': { site:"Youtube", icon: faYoutube },
    'youtu.be': { site:"Youtube", icon: faYoutube },
    'vimeo.com': { site: "Vimeo", icon: faVimeo },
    ...
}
...
const videoSource = (hostname in domains) ? domains[hostname].site : "Unknown"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ended up using an array of objects but I did something similar to make it much less verbose.

<Grid item xs={12} sx={{ maxHeight: '0px', padding: '0 !important', margin: '0' }}>
<VideoOverlay
videoOverlayTitle={videoOverlayTitle}
source={getVideoSource(videoWidget.video_url)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

calculate the video source once and store it as a constant instead of querying it repeatedly inside your JSX. Same goes for the title.

Comment on lines 218 to 236
const getIcon = (source: string) => {
if ('YouTube' === source) {
return faYoutube;
} else if ('Vimeo' === source) {
return faVimeo;
} else if ('Facebook' === source) {
return faFacebookF;
} else if ('Twitch' === source) {
return faTwitch;
} else if ('SoundCloud' === source) {
return faSoundcloud;
} else if ('Mixcloud' === source) {
return faMixcloud;
} else if ('DailyMotion' === source) {
return faDailymotion;
} else {
return faQuestionCircle;
}
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, please replace this large conditional with an object map or other solution. I recommend using the same map to get the source name and icon because they are related 1-1

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Used the same object array from before to solve this issue.

return `${player.getInternalPlayer().element.title} (${source} video)`;
} else if ('Facebook' === source || 'Twitch' === source || 'DailyMotion' === source) {
return `${widgetTitle} (${source} video)`; // API doesn't return video title, use widget title.
} else if ('SoundCloud' === source || 'Mixcloud' === source) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like sound platforms are adding a lot of complexity to your title parsing. Were these added platforms requested by the PO?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

None of the platforms were added, they were just always enabled. All I'm doing is handling all of the possible inputs. Of course, we can change this by URL checking with YUP on the form if it comes to that.

icon={getIcon(source)}
style={{ fontSize: '1.9rem', paddingRight: '0.65rem', color: '#FCBA19' }}
/>{' '}
<span style={{ fontSize: '0.95rem' }}>{videoOverlayTitle}</span>
Copy link
Contributor

@NatSquared NatSquared Sep 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please stop creating text with size 0.95rem or 0.9rem. This does not correspond to any value used in the designs, and causes rendering issues because it comes out to a fractional pixel value.
To match the designs in Figma, use one of the following:

  1. <BodyText size="small">
  2. fontSize: '0.875rem'
  3. fontSize: '14px'

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Must have missed the exact value, will update. Doesn't really seem to be an issue since browsers have been parsing fractional pixel values for a long time. Please be patient, it's important to be gracious when teaching each other things.

Copy link
Contributor

@NatSquared NatSquared left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your work on these tickets Jareth! A few things for you to take a look at 👀

…s, fixed issues with deferred data aborted errors.
@@ -39,7 +39,7 @@ export const getLanguageValue = (currentLanguage: string, languages: Language[])
const AuthoringTemplate = () => {
const { onSubmit, defaultValues, setDefaultValues, fetcher }: AuthoringContextType = useOutletContext();
const { engagementId } = useParams() as { engagementId: string }; // We need the engagement ID quickly, so let's grab it from useParams
const { engagement } = useRouteLoaderData('single-engagement') as { engagement: Engagement };
const { engagement } = useLoaderData() as { engagement: Promise<Engagement> };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const { engagement } = useLoaderData() as { engagement: Promise<Engagement> };
const { engagement } = useLoaderData() as EngagementLoaderData;

)}
</Await>
</Suspense>
<div style={{ marginTop: '2rem' }}>
Copy link
Contributor

@NatSquared NatSquared Sep 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for resolving this layout shift issue :)

Copy link
Contributor

@NatSquared NatSquared left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for looking at my suggestions Jareth! I would still recommend refactoring your timestamp calculation to be more readable and I would appreciate you addressing the description_title controller but I won't block you from merging :)

return 'Unknown';
}
};
const videoSources: WidgetVideoSource[] = [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really appreciate this cleaned up approach :)

Copy link

sonarcloud bot commented Sep 20, 2024

@jareth-whitney jareth-whitney merged commit f51812c into main Sep 20, 2024
14 of 15 checks passed
@jareth-whitney jareth-whitney deleted the feature/deseng692 branch September 20, 2024 20:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants